/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Red;
import java.util.HashMap;
import java.util.Random;
import java.text.DecimalFormat;

public class Red {

    double [][] pesosEO;
    double [][] pesosOS;
    
    double[] yO;
    double[] yS;

    public final double max = 0.3 ;
    public final double min = -0.3;
    public final double alfa = 0.8;
    public final double errorAceptable = 0.1;
//
    int numEntrada = 2500;
    int numSalida = 4;
    int numOculta = 100;

    DecimalFormat df;

    public Red(int entradas, int salida){
        pesosEO = new double[numEntrada][numOculta];
        pesosOS = new double[numOculta][numSalida];
        yO = new double[100];
        yS = new double[4];

        df = new DecimalFormat("0.00000");

    }

/* Inicializa las dos matrices de pesos (Entrada-Oculta y Oculta-Salida) con números aleatorios
 * invocando el método randomDouble, que generan doubles random entre el min y max establecido (-0.3 y 0.3).
 */
    public void inicializar(){

        Random random = new Random();
        for (int fila = 0; fila < pesosEO.length; fila++) {
            for (int columna = 0; columna < pesosEO[0].length; columna++) {
                pesosEO[fila][columna] = randomDouble(min,max, random);
            }
        }
        for (int fila = 0; fila < pesosOS.length; fila++) {
            for (int columna = 0; columna < pesosOS[0].length; columna++) {
                pesosOS[fila][columna] = randomDouble(min,max, random);
            }
        }
    }

/* Busca un número random especificado por los parámetros, en donde inicio es el valor menor
 * del intervalo de números random y numFinal el valor mayor. En este caso, este rango se estableció
 * con los parámetros de la red min y max.
 */
    public double randomDouble(double inicio, double numFinal, Random aRandom){
        if ( inicio > numFinal ) {
          throw new IllegalArgumentException("El Inicio del intervalo de números random no puede ser menor que el número Final del intervalo");
        }
        double rango = numFinal - inicio ;
        double fraction = (double)(rango * aRandom.nextDouble());
        double numRandom = (fraction + inicio);
        //System.out.println("Generated : " + randomNumber);
    return numRandom;

  }

/* Calcula el valor del neto de la entrada multiplicado por el peso que va de la
 * capa de entrada a la capa Oculta
 */
    void calcularYO(double[] entrada){
        int netoOculta =0;
        for (int i = 0; i < yO.length; i++){
            for (int j = 0; j < entrada.length; j++) {
                netoOculta += entrada[j]*pesosEO[j][i];
            }
            yO[i] = 1.0/(1.0+Math.exp(-netoOculta));
        }
    }

/* Calcula el valor del neto de la capa de salida multiplicando el peso que va de la
 * capa oculta a la capa de salida por el valor achatado de la capa Oculta (YO).
 */
    void calcularYS(){
        int netoSalida =0;
        for (int i = 0; i < yS.length; i++) {
            for (int j = 0; j < yO.length; j++) {
                netoSalida += yO[j]*pesosOS[j][i];
            }
            yS[i] = 1.0/(1.0+Math.exp(-netoSalida));
        }
    }


    public void entrenar(HashMap<double[], double[]> conjunto){

        //PRIMERO INCIALIZA LAS MATRICES DE PESOS CON NUMEROS RANDOM DENTRO
        //DEL RANGO ESTABLECIDO(-0.3,0.3)
        this.inicializar();
        boolean converge = false;
        int contador = 0;

        while(!converge){
            contador++;
            for(double[] entrada : conjunto.keySet()){

                //CALCULA LOS NETOS Y ACHATAMIENTO DE LA CAPA OCULTA SEGUIDO POR LA DE SALIDA//
                calcularYO(entrada);
                calcularYS();

                //DETERMINA SI EL ERROR ESTÁ DENTRO DEL RANGO ACEPTABLE//
                if(!errorDentroRango(conjunto.get(entrada))){

                    //SI NO, ENTONCES LLAMA A LA FUNCIÓN AJUSTAR PESOS//
                    converge = false;
                    ajustarPesos(entrada,conjunto.get(entrada));

                 }else{
                    //DE LO CONTRARIO CONVERGIÓ//
                    converge = true;
                    System.out.println("Convergió en la iteración #: "+contador);
                 }
             }
         }
     }

    public void ajustarPesos(double[] entrada, double[] d){
        double factorCambioS = 0;
        double factorCambioO = 0;
        double sumatoria = 0;

        //AJUSTE DE PESOS DE LA CAPA OCULTA A LA CAPA DE SALIDA//
         for(int i=0; i<pesosOS.length; i++){                      //yO
            for(int j=0; j<pesosOS[0].length; j++){                //d
                factorCambioS = (d[j]-yS[j])*yS[j]*(1-yS[j]);
                //System.out.println("");
                pesosOS[i][j] = pesosOS[i][j] + (alfa * factorCambioS * yO[i]);
            }
        }
         
        //AJUSTE DE PESOS DE LA CAPA DE ENTRADA A LA CAPA OCULTA//
        for(int i=0; i<pesosOS.length; i++){                      //yO
            for(int j=0; j<pesosOS[0].length; j++){                //d
                    sumatoria += (d[j]-yS[j])*yS[j]*(1-yS[j]*pesosOS[i][j]);
                    //System.out.println("");
                }
            factorCambioO = yO[i]*(1-yO[i])*sumatoria;
            for(int k=0; k<pesosEO.length; k++){
                 pesosEO[i][k]= pesosEO[i][k]+ (alfa * factorCambioO * entrada[k-1]);
            }
        }
    }


/* Calcula si el resultado del achatamiento de las neuronas de salida cumple
 * el margen de error pre-establecido.
 */
    public boolean errorDentroRango(double[] valorEsperado){
        boolean resultado = false;
        double error = -1;

        for(int i=0; i<valorEsperado.length; i++){
            error = Math.abs(valorEsperado[i]-yS[i]);
            if( error < errorAceptable){
                resultado = true;
                break;
            }
        }
        return resultado;
    }


}
